package com.uuah.server;

import java.io.UnsupportedEncodingException;
import java.util.EventObject;
import java.util.Iterator;

import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.exception.AbstractUuahException;
import com.uuah.exception.UuahException;
import com.uuah.server.api.IUuahContext;
import com.uuah.server.api.IUuahEvent;
import com.uuah.server.api.IUuahMessage;
import com.uuah.server.api.IThreadSafeAccess;
import com.uuah.server.api.component.service.IService;
import com.uuah.server.api.endpoint.IUuahEndpoint;
import com.uuah.server.api.session.IUuahSession;
import com.uuah.server.config.Constants;
import com.uuah.server.config.i18n.ServerMessages;
import com.uuah.server.exception.transformer.TransformerException;
import com.uuah.server.session.UuahSession;
import com.uuah.server.transport.PropertyScope;
import com.uuah.utils.MapUtils;
import com.uuah.utils.UUID;

public class UuahEvent extends EventObject implements IUuahEvent,
		IThreadSafeAccess {
	private static final long serialVersionUID = 1L;
	protected transient static Logger logger = LoggerFactory
			.getLogger(UuahEvent.class.getName());

	private transient IUuahEndpoint endpoint = null;

	private String id = null;
	private IUuahMessage message = null;
	private IUuahSession session;
	private boolean synchronous = false;
	private int timeout = TIMEOUT_NOT_SET_VALUE;
	private transient Object transformedMessage = null;
	protected String[] ignoredPropertyOverrides = new String[] { Constants.UUAH_METHOD_PROPERTY };

	public UuahEvent(IUuahMessage message, IUuahEndpoint endpoint,
			IService service, IUuahEvent previousEvent) {
		super(message.getPayload());
		this.message = message;
		this.id = generateEventId();
		this.session = previousEvent.getSession();
		((UuahSession) session).setService(service);
		this.endpoint = endpoint;
		this.synchronous = previousEvent.isSynchronous();
		this.timeout = previousEvent.getTimeout();
		fillProperties(previousEvent);
	}

	public UuahEvent(IUuahMessage message, IUuahEndpoint endpoint,
			IUuahSession session, boolean synchronous) {
		super(message.getPayload());
		this.message = message;
		this.endpoint = endpoint;
		this.session = session;
		this.id = generateEventId();
		this.synchronous = synchronous;
		fillProperties(null);
	}

	public UuahEvent(IUuahMessage message, IUuahEndpoint endpoint,
			IUuahSession session, String eventId, boolean synchronous) {
		super(message.getPayload());
		this.message = message;
		this.endpoint = endpoint;
		this.session = session;
		this.id = eventId;
		this.synchronous = synchronous;
		fillProperties(null);
	}

	public UuahEvent(IUuahMessage message, IUuahEvent rewriteEvent) {
		super(message.getPayload());
		this.message = message;
		this.id = rewriteEvent.getId();
		this.session = rewriteEvent.getSession();
		((UuahSession) session).setService(rewriteEvent.getService());
		this.endpoint = rewriteEvent.getEndpoint();
		this.synchronous = rewriteEvent.isSynchronous();
		this.timeout = rewriteEvent.getTimeout();

		if (rewriteEvent instanceof IUuahEvent) {
			this.transformedMessage = ((UuahEvent) rewriteEvent)
					.getCachedMessage();
		}
		fillProperties(rewriteEvent);
	}

	protected void fillProperties(IUuahEvent previousEvent) {
		if (previousEvent != null) {
			IUuahMessage msg = previousEvent.getMessage();
			synchronized (msg) {
				for (Iterator iterator = msg.getPropertyNames().iterator(); iterator
						.hasNext();) {
					String prop = (String) iterator.next();
					Object value = msg.getProperty(prop);
					if (!ignoreProperty(prop)) {
						message.setProperty(prop, value);
					}
				}
			}
		}

		if (endpoint != null && endpoint.getProperties() != null) {
			for (Iterator iterator = endpoint.getProperties().keySet()
					.iterator(); iterator.hasNext();) {
				String prop = (String) iterator.next();
				Object value = endpoint.getProperties().get(prop);
				if (!ignoreProperty(prop)) {
					message.setProperty(prop, value, PropertyScope.INVOCATION);
				}
			}
		}
	}

	protected boolean ignoreProperty(String key) {
		if (key == null) {
			return true;
		}

		for (int i = 0; i < ignoredPropertyOverrides.length; i++) {
			if (key.equals(ignoredPropertyOverrides[i])) {
				return false;
			}
		}

		return null != message.getProperty(key);
	}

	Object getCachedMessage() {
		return transformedMessage;
	}

	public IUuahMessage getMessage() {
		return message;
	}

	public byte[] getMessageAsBytes() throws UuahException {
		try {
			return message.getPayloadAsBytes();
		} catch (Exception e) {
			throw new UuahException(ServerMessages
					.cannotReadPayloadAsBytes(message.getPayload().getClass()
							.getName()), e);
		}
	}

	public Object transformMessage() throws TransformerException {
		return transformMessage(null);
	}

	public Object transformMessage(Class outputType)
			throws TransformerException {
		message.applyTransformer(endpoint.getRequestTransformer());
		if (outputType == null) {
			return message.getPayload();
		} else {
			return message.getPayload(outputType);
		}
	}

	public byte[] transformMessageToBytes() throws TransformerException {
		return (byte[]) transformMessage(byte[].class);
	}

	public String transformMessageToString() throws TransformerException {
		try {
			return new String(transformMessageToBytes(), getEncoding());
		} catch (UnsupportedEncodingException e) {
			throw new TransformerException(endpoint.getRequestTransformer(), e);
		}
	}

	public String getMessageAsString() throws AbstractUuahException {
		return getMessageAsString(getEncoding());
	}

	public String getMessageAsString(String encoding)
			throws AbstractUuahException {
		try {
			return message.getPayloadAsString(encoding);
		} catch (Exception e) {
			throw new UuahException(ServerMessages
					.cannotReadPayloadAsString(message.getClass().getName()), e);
		}
	}

	public String getId() {
		return id;
	}

	public Object getProperty(String name, boolean exhaustiveSearch) {
		return getProperty(name, null, exhaustiveSearch);
	}

	public Object getProperty(String name, Object defaultValue,
			boolean exhaustiveSearch) {
		Object property = message.getProperty(name);

		if (exhaustiveSearch) {
			if (property == null) {
				property = MapUtils.getObject(getEndpoint().getEndpointURI()
						.getParams(), name, null);
			}

			if (property == null) {
				try {
					property = PropertyUtils.getProperty(getEndpoint()
							.getConnector(), name);
				} catch (Exception e) {

				}
			}
		}
		return (property == null ? defaultValue : property);
	}

	public IUuahEndpoint getEndpoint() {
		return endpoint;
	}

	public String toString() {
		StringBuffer buf = new StringBuffer(64);
		buf.append("UuahEvent: ").append(getId());
		buf.append(", sync=").append(isSynchronous());
		buf.append(", ").append(endpoint);

		return buf.toString();
	}

	protected String generateEventId() {
		return UUID.getTimeBasedUUIDString();
	}

	public IUuahSession getSession() {
		return session;
	}

	void setSession(IUuahSession session) {
		this.session = session;
	}

	public IService getService() {
		return session.getService();
	}

	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof UuahEvent)) {
			return false;
		}

		final UuahEvent event = (UuahEvent) o;

		if (message != null ? !message.equals(event.message)
				: event.message != null) {
			return false;
		}
		return id.equals(event.id);
	}

	public int hashCode() {
		return 29 * id.hashCode() + (message != null ? message.hashCode() : 0);
	}

	public boolean isSynchronous() {
		return synchronous;
	}

	public void setSynchronous(boolean value) {
		synchronous = value;
	}

	public int getTimeout() {
		if (timeout == TIMEOUT_NOT_SET_VALUE) {
		}
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public String getEncoding() {
		String encoding = message.getEncoding();
		if (encoding == null) {
			encoding = endpoint.getEncoding();
		}

		return encoding;
	}

	public IUuahContext getUuahContext() {
		return endpoint.getUuahContext();
	}

	public IThreadSafeAccess newThreadCopy() {
		if (message instanceof IThreadSafeAccess) {
			UuahEvent copy = new UuahEvent(
					(IUuahMessage) ((IThreadSafeAccess) message).newThreadCopy(),
					this);
			copy.resetAccessControl();
			return copy;
		} else {
			return this;
		}
	}

	public void resetAccessControl() {
		if (message instanceof IThreadSafeAccess) {
			((IThreadSafeAccess) message).resetAccessControl();
		}
	}

	public void assertAccess(boolean write) {
		if (message instanceof IThreadSafeAccess) {
			((IThreadSafeAccess) message).assertAccess(write);
		}
	}
}
